package com.swak.frame.resolver;
import java.io.IOException;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSON;
import com.swak.frame.dto.base.SysRestCode;
import com.swak.frame.dto.response.Responses;
import com.swak.frame.dto.response.RpcResponse;
import com.swak.frame.exception.ApiException;
import com.swak.frame.logger.SwakLoggerFactory;

/**
 * common exception resolver
 *
 * 1、@ControllerAdvice：扫描所有Controller；
 * 2、@ControllerAdvice(annotations=RestController.class)：扫描指定注解类型的Controller；
 * 3、@ControllerAdvice(basePackages={"com.aaa","com.bbb"})：扫描指定package下的Controller
 *
 */

public class SwakWebExceptionResolver implements HandlerExceptionResolver {

	private final Logger logger = SwakLoggerFactory.getLogger(getClass());

	@Override
	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
			Exception ex) {
		// if json
		boolean isJson = isJsonRequest(handler);
		logger.error("WebExceptionResolver error,request path:{}", request.getRequestURI());
		logger.error("WebExceptionResolver error message:" + ex.getMessage(), ex);
		RpcResponse.Builder<?> builder = Responses.builder(SysRestCode.SYS_ERR);

		if (ex instanceof ApiException) {
			ApiException apiExc = (ApiException) ex;
			builder.of(apiExc.getErrCode(), apiExc.getErrMessage());
			return handlerModelAndView(response, builder.build(), isJson);
		}

		if (ex instanceof MissingServletRequestParameterException) {
			MissingServletRequestParameterException argumentType = (MissingServletRequestParameterException) ex;
			builder.of(SysRestCode.INVALID_PARAM).retMessage(argumentType.getParameterName() + "入参必填");
			return handlerModelAndView(response, builder.build(), isJson);
		}

		if (ex instanceof MethodArgumentTypeMismatchException) {
			MethodArgumentTypeMismatchException argumentType = (MethodArgumentTypeMismatchException) ex;
			builder.of(SysRestCode.INVALID_PARAM).retMessage(argumentType.getName() + "入参数据格式异常");
			return handlerModelAndView(response, builder.build(), isJson);
		}

		if (ex instanceof ConstraintViolationException) {
			return handerConstraintViolationException(response, isJson, builder, (ConstraintViolationException) ex);
		}

		if (ex instanceof MethodArgumentNotValidException) {
			return validationExceptionHandler(response, isJson, builder, ex);
		}

		if (ex instanceof BindException) {
			return validationExceptionHandler(response, isJson, builder, ex);
		}

		// 系统级别异常
		return handlerModelAndView(response, builder.build(), isJson);
	}
	
	
	public boolean isJsonRequest(Object handler) {
		boolean isJson = false;
		HandlerMethod method = (HandlerMethod) handler;
		Class<?> clazz = method.getMethod().getDeclaringClass();
		RestController restController = AnnotationUtils.findAnnotation(clazz, RestController.class);
		if (restController != null) {
			isJson = true;
		}
		if (!isJson) {
			ResponseBody responseBody = AnnotationUtils.getAnnotation(method.getMethod(), ResponseBody.class);
			if (responseBody != null) {
				isJson = true;
			}
		}
		
		return isJson;
	}

	/**
	 * ConstraintViolationException
	 * 
	 * @param response
	 * @param isJson
	 * @param builder
	 * @param ex
	 * @return
	 */
	private ModelAndView handerConstraintViolationException(HttpServletResponse response, boolean isJson,
			RpcResponse.Builder<?> builder, ConstraintViolationException ex) {
		ConstraintViolationException violationException = (ConstraintViolationException) ex;
		Set<ConstraintViolation<?>> violations = violationException.getConstraintViolations();
		if (CollectionUtils.isNotEmpty(violations)) {
			for (ConstraintViolation<?> item : violations) {
				builder.of(SysRestCode.INVALID_PARAM.getRetCode(), item.getMessage());
				break;
			}
		}
		return handlerModelAndView(response, builder.build(), isJson);
	}

	/**
	 * BindException.class, MethodArgumentNotValidException.class
	 * 
	 * @param response
	 * @param isJson
	 * @param builder
	 * @param ex
	 * @return
	 */
	public ModelAndView validationExceptionHandler(HttpServletResponse response, boolean isJson,
			RpcResponse.Builder<?> builder, Exception ex) {
		BindingResult bindResult = null;
		if (ex instanceof BindException) {
			bindResult = ((BindException) ex).getBindingResult();
		} else if (ex instanceof MethodArgumentNotValidException) {
			bindResult = ((MethodArgumentNotValidException) ex).getBindingResult();
		}
		if (bindResult != null && bindResult.hasErrors()) {
			String errorMessage = bindResult.getAllErrors().get(0).getDefaultMessage();
			builder.of(SysRestCode.INVALID_PARAM.getRetCode(), errorMessage);
		}
		return handlerModelAndView(response, builder.build(), isJson);
	}

	public ModelAndView handlerModelAndView(HttpServletResponse response, RpcResponse<?> respResult, boolean isJson) {
		if (isJson) {
			try {
				response.setContentType("application/json;charset=utf-8");
				response.getWriter().print(JSON.toJSONString(respResult));
			} catch (IOException e) {
				logger.error(e.getMessage(), e);
			}
			return new ModelAndView();
		}
		ModelAndView mv = new ModelAndView();
		mv.addObject("retMessage", respResult.getRetMessage());
		mv.addObject("retCode", respResult.getRetCode());
		mv.setViewName("error/exception");
		return mv;
	}

}